#define PrynEditorInternal
#define PrynEditorImplementation
#include <prynEditor.h>
#include <prynGui.h>

static PrynGuiMenu *prynFactoryMenu;

static PrynColor background ()
{
	static int code = 0;
	int value = ((code ++) % 16) * 255 / 15;
	
	return prynColorRGB (value, value, value);
}

static PrynResult onMouseMove (PrynGuiEvent *event, void *data)
{
	PrynGuiControl *control = 0;
	PrynEditorBoard *board = (PrynEditorBoard *) data;

	int oldMouseX = board->mouseX, oldMouseY = board->mouseY;
	int mouseX, mouseY;
	int mouseChangeX, mouseChangeY;
	PrynEditorComponent *over;
	PrynPin *overPin;
	PrynRectangle area;

	prynGuiEventControl (event, &control);
	prynGuiEventMousePosition (event, &mouseX, &mouseY);
	board->mouseX = mouseX, board->mouseY = mouseY;
	mouseChangeX = mouseX - oldMouseX, mouseChangeY = mouseY - oldMouseY;

	prynEditorBoardShuffle (board, 10, 20);
	
	prynEditorBoardPickComponentOrPin (board, mouseX, mouseY, &over, &overPin);
	
switchAgain:
	switch (board->action)
	{
		case PrynEditorBoardActionNone:
			if (over != board->over || overPin != board->overPin)
			{
				prynEditorComponentDirty (over);
				prynEditorComponentDirty (board->over);
				board->over = over;
				board->overPin = overPin;
			}
			break;
		
		case PrynEditorBoardActionClick:
			board->action = board->overPin ? PrynEditorBoardActionDraggingPin : PrynEditorBoardActionDraggingComponent;
			goto switchAgain;
			
		case PrynEditorBoardActionDraggingComponent:
			prynEditorComponentMove (board->over, board->over->x + mouseChangeX, board->over->y + mouseChangeY);
			break;
			
		case PrynEditorBoardActionDraggingPin:
			if (overPin != board->targetPin)
			{
				if (overPin) prynEditorComponentDirty (over);
				if (board->targetPin) prynEditorComponentDirty (board->target);
				board->targetPin = overPin;
				board->target = over;
			}
			
			prynRectangleContaining (&area, 2, board->dragStartX, board->dragStartY, mouseX, mouseY);
			prynRectangleExpand (&area, &area, 1, 1);
			prynGuiControlDirtyRectangle (control, &area);

			prynRectangleContaining (&area, 2, board->dragStartX, board->dragStartY, oldMouseX, oldMouseY);
			prynRectangleExpand (&area, &area, 1, 1);
			prynGuiControlDirtyRectangle (control, &area);
			//prynGuiControlDirty (control);
			break;
	}
	return PrynResultSuccess;
}

static PrynResult onMouseLeftButtonDown (PrynGuiEvent *event, void *data)
{
	PrynEditorBoard *board = (PrynEditorBoard *) data;
	PrynEditorComponent *over = board->over;
	
	if (board->action || !over)
		return PrynResultDone;
	
	board->dragStartX = board->mouseX;
	board->dragStartY = board->mouseY;
	prynDListRemove (board->components, over);
	prynDListAppend (board->components, over);
	prynEditorComponentDirty (over);
	board->action = PrynEditorBoardActionClick;
	return 0;
}

static PrynResult onMouseLeftButtonUp (PrynGuiEvent *event, void *data)
{
	PrynGuiControl *control;
	PrynEditorBoard *board = (PrynEditorBoard *) data;
	PrynState *state = board->state;
	PrynResult result;
	PrynRectangle area;

	prynGuiEventControl (event, &control);
	
	switch (board->action)
	{
		case PrynEditorBoardActionClick:
			if (!board->overPin && prynComponentClick (board->over->component) == PrynResultSuccess)
				prynEditorComponentDirty (board->over);
			// fallthrough
		case PrynEditorBoardActionDraggingComponent:
			board->action = PrynEditorBoardActionNone;
			prynEditorBoardShuffle (board, 10, 20);
			break;
		
		case PrynEditorBoardActionDraggingPin:
			prynRectangleContaining (&area, 2, board->dragStartX, board->dragStartY, board->mouseX, board->mouseY);
			prynRectangleExpand (&area, &area, 1, 1);
			prynGuiControlDirtyRectangle (control, &area);
			
			if (board->target)
			{
				PrynEditorPin *editor = 0;

				prynEditorComponentDirty (board->target);
				result = prynPinConnect (board->overPin, board->targetPin);
				prynEditorPinFrom (&editor, board->over, board->overPin);
				prynEditorPinUpdateRoute (editor);
				board->pathDirty = true;
				board->target = 0;
				board->targetPin = 0;
			}
			
			board->action = PrynEditorBoardActionNone;
			//prynEditorBoardShuffle (board, 10, 20);
			prynGuiControlDirty (control);
			if (result < 0) prynGuiReportError (state, result);
			break;
			
		default:
			break;
	}
	
	return 0;
}

static PrynResult onMouseRightButtonDown (PrynGuiEvent *event, void *data)
{
	PrynEditorBoard *board = (PrynEditorBoard *) data;
	PrynGuiControl *control = 0;
	PrynState *state = board->state;
	int cx, cy;
	int x = 0, y = 0;
	PrynFactory *factory;
	PrynComponent *component = 0;
	PrynResult result;

	prynGuiEventControl (event, &control);
	
	if (board->action)
		return PrynResultDone;
			
	prynGuiEventMousePosition (event, &cx, &cy);
	
	prynGuiControlGetClientOffsetInScreen (control, &x, &y);
	x += cx, y += cy;
	
	if (!prynFactoryMenu)
	{
		PrynGuiMenu *menu = 0;
		PrynFactory *factory;
		
		prynGuiMenuCreatePopup (&menu, state);
		prynFactoryMenu = menu;

		for (factory = 0; !prynStateIterateFactories (state, &factory); )
		{
			PrynGuiMenuItem item;
			PrynString name;

			prynFactoryName (factory, &name);
			prynGuiMenuAppendItem (menu, &name, (uintptr_t) factory, &item);
		}
	}
	
	prynGuiMenuDisplayData (prynFactoryMenu, x, y, control, (PrynGuiMenuData *) &factory);
	result = prynComponentCreate (&component, factory);
	
	if (result >= 0)
	{
		PrynEditorComponent *editor = 0;

		prynEditorComponentFrom (&editor, board, component);
		if (editor)
			prynEditorComponentMove (editor, cx, cy);
		
		if (result < 0) prynGuiReportError (state, result);
	}
	else if (factory)
		prynGuiReportError (state, result);
	
	return 0;
}

static PrynResult onMouseMiddleButtonDown (PrynGuiEvent *event, void *data)
{
	PrynStartFunction ();
	PrynEndFunction ();
}

static PrynResult onMouseMiddleButtonUp (PrynGuiEvent *event, void *data)
{
	PrynStartFunction ();
	PrynEndFunction ();
}

static void onPaintTimer (PrynGuiControl *control, void *data)
{
	PrynEditorBoard *board = (PrynEditorBoard *) data;
	
	board->ticks ++;
	prynEditorBoardDirtyConnections (board);
	if ((board->ticks % 20) == 0)
	{
		prynPrintMemoryUpdates (board->state);
	}
}

static PrynResult onPaint (PrynGuiEvent *event, void *data)
{
	PrynEditorBoard *board = (PrynEditorBoard *) data;
	PrynGuiPainter *painter;
	PrynGuiControl *control;
	PrynResult result;
	
	prynGuiEventPainter (event, &painter);
	prynGuiEventControl (event, &control);
	result = prynEditorBoardPaint (board, painter);
	prynGuiControlTimer (control, 50, &onPaintTimer, board);
	return result;
}

static PrynResult onDestroy (PrynGuiEvent *event, void *data)
{
	PrynEditorBoard *board = (PrynEditorBoard *) data;
	
	if (board)
		board->control = 0;
	prynGuiQuitMessageLoop (board->state);
	return 0;
}

PrynEditorExport (PrynResult, prynEditorBoardCreateFromControl, (PrynEditorBoard **output, PrynGuiControl *control))
{
	PrynStartFunction ();
	PrynEditorBoard *board;
	PrynString tagName;
	PrynObject object;
	PrynTag *tag;

	if (output) *output = 0;
	prynAssert (control, Failure);
	prynCall (prynGuiControlState (control, &state));
	prynCall (prynAllocateClearedObject (state, &board));
	prynAssert (board, OutOfMemory);

	prynStringLiteral (&tagName, "Pryn.EditorBoard");
	prynGuiControlToObject (control, &object);
	prynTagCreate (&tag, &object, &tagName, board);	
	
	prynGuiControlRegisterReceiver (control, PrynGuiEventTypePaint, board, &onPaint, 0);
	prynGuiControlRegisterReceiver (control, PrynGuiEventTypeMouseMove, board, &onMouseMove, 0);
	prynGuiControlRegisterReceiver (control, PrynGuiEventTypeMouseLeftButtonDown, board, &onMouseLeftButtonDown, 0);
	prynGuiControlRegisterReceiver (control, PrynGuiEventTypeMouseLeftButtonUp, board, &onMouseLeftButtonUp, 0);
	prynGuiControlRegisterReceiver (control, PrynGuiEventTypeMouseRightButtonDown, board, &onMouseRightButtonDown, 0);
	prynGuiControlRegisterReceiver (control, PrynGuiEventTypeMouseMiddleButtonDown, board, &onMouseMiddleButtonDown, 0);
	prynGuiControlRegisterReceiver (control, PrynGuiEventTypeMouseMiddleButtonUp, board, &onMouseMiddleButtonUp, 0);
	
	board->control = control;
	board->state = state;
	
	PrynEndFunctionAfterSuccess ({
		if (output) *output = board;
	});
}

PrynEditorExport (PrynResult, prynEditorBoardCreateFromHwnd, (PrynEditorBoard **output, PrynState *state, void *window))
{
	PrynResult result = PrynResultSuccess;
	PrynGuiControl *control;
	
	if (output) *output = 0;
	if (!output || !state || !window) return PrynResultFailure;
	
	prynGuiControlCreateFromHwnd (&control, state, window);
	if (!control) return PrynResultFailure;
	return prynEditorBoardCreateFromControl (output, control);
}

PrynEditorExport (PrynResult, prynEditorBoardCreate, (PrynEditorBoard **output, PrynState *state, const PrynString *title))
{
	PrynGuiControl *window = 0;
	PrynResult result;
	PrynEditorBoard *board = 0;

	prynGuiControlCreateWindow (&window, state, title, 640, 480);

	if (output) *output = 0;
	if (!window) return PrynResultFailure;
	result = prynEditorBoardCreateFromControl (&board, window);
	prynGuiControlRegisterReceiver (window, PrynGuiEventTypeDestroy, board, &onDestroy, 0);
	prynGuiControlShow (window);
	if (output) *output = board;
	return result;
}

PrynBaseLibraryEntryPoint (void *instance)
{
}

